﻿using System;

namespace BizElements.Core
{
    /// <summary>Binds/maps properties of generated class to columns and relations using reflection.</summary>
    public class EntityPropertyBinder : IEntityPropertyBinder
    {
        /// <summary>Gets or sets bound entity.</summary>
        public virtual IEntity BoundEntity { get; set; }

        /// <summary>Resets the specified field to default value. Same as using SetField method with null argument.</summary>
        /// <param name="field">Field that is to be reset. Throws exception if an invalid field is given.</param>
        public virtual void ResetField(IDbColumn field)
        {
            ReflectionUtil.SetPropertyValue(this.BoundEntity, field.PropertyName, field.DefaultValue);
        }

        /// <summary>Sets the given value into specified field.</summary>
        /// <param name="field">Field. Throws exception if an invalid field is given.</param>
        /// <param name="value">Value. If null then the field is reset to default value.</param>
        public virtual void SetField(IDbColumn field, object value)
        {
            if (value == null)
                ResetField(field);
            else
                ReflectionUtil.SetPropertyValue(this.BoundEntity, field.PropertyName, value);
        }

        /// <summary>Gets the value contained in the specified field.</summary>
        /// <param name="field">Field. Throws exception if an invalid field is given.</param>
        /// <returns>Value.</returns>
        public virtual object GetField(IDbColumn field)
        {
            return ReflectionUtil.GetPublicPropertyValue(this.BoundEntity, field.PropertyName);
        }

        /// <summary>Checks whether the specified field contains a <b>null</b> reference or it hasn't been initialized.</summary>
        /// <param name="field">Field. Throws exception if an invalid field is given.</param>
        /// <returns><b>true</b> if the field contains <b>null</b> not initialized; <b>false</b> otherwise.</returns>
        public virtual bool IsNull(IDbColumn field)
        {
            object value = GetField(field);
            bool isNull = (value == null);
            return isNull;
        }

        /// <summary>Gets the parent entity defined by the given foreign key.</summary>
        /// <param name="foreignKey">FK. Child table of the given FK must be the current entity's table; 
        /// otherwise an exception is generated.</param>
        /// <returns>Parent entity. <b>Null</b> if the FK fields haven't been set or if the entity with the given key values doesn't exist.</returns>
        /// <remarks>If the entity hasn't been initialized yet but the FK values are set and relationship navigation
        /// is enabled then the method will automatically fetch it from the data source.</remarks>
        public virtual IEntity GetParent(DbRelation foreignKey)
        {
            string propertyName = this.BoundEntity.Table.GetParentProperty(foreignKey, null);
            IEntity parent = ReflectionUtil.GetPublicPropertyValue(this.BoundEntity, propertyName) as IEntity;
            return parent;
        }

        /// <summary>Sets the given value into the member that represents the parent entity defined by the foreign key.</summary>
        /// <param name="foreignKey">FK. Child table of the given FK must be the current entity's table; 
        /// otherwise an exception is generated.</param>
        /// <param name="entity">Parent entity. Must be compatibile with the targeted member.</param>
        /// <remarks>Inherited classes must implement the required logic.</remarks>
        public virtual void SetParent(DbRelation foreignKey, IEntity entity)
        {
            string propertyName = this.BoundEntity.Table.GetParentProperty(foreignKey, null);
            ReflectionUtil.SetPropertyValue(this.BoundEntity, propertyName, entity);
        }
    }
}
